﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Scripting.Runtime;

namespace DlrBasic
{
    /// <summary>
    /// Utility class with some runtime functionality such as I/O, screen control,
    /// sound events, random number.
    /// </summary>
    public static class DlrBasicHelpers
    {
        static Random basicRandom = new Random(0);
        static int currentColumn = 0;

        public static int CurrentColumn
        {
            get { return currentColumn; }
        }

        public static float GetNextRandom()
        {
            double value = basicRandom.NextDouble();
            return (float)value;
        }

        public static void RandomizeWithInput(CodeContext context)
        {
            Print(context, Properties.Resources.RandomSeedPrompt);
            object[] values = Input(context, new Type[] { typeof(short) }, true);

            basicRandom = new Random((short)values[0]);
        }

        public static void Randomize(double n)
        {
            basicRandom = new Random((n + basicRandom.NextDouble()).GetHashCode());
        }

        public static void ClearScreen()
        {
            Console.Clear();
        }

        public static void Debug(object o)
        {
            System.Diagnostics.Debug.WriteLine("DLRBASIC: " + o.ToString());
        }

        public static string InputKey()
        {
            return Console.ReadKey(true).KeyChar.ToString();
        }

        public static object[] Input(CodeContext context, Type[] objectTypes, bool printQuestion)
        {
            string line = InputLine(context, printQuestion);
            while (line != null)
            {
                string[] parts = line.Split(',');
                try
                {
                    if (parts.Length >= objectTypes.Length)
                    {
                        object[] values = new object[objectTypes.Length];
                        for (int i = 0; i < objectTypes.Length; i++)
                        {
                            values[i] = ConvertTo(objectTypes[i], parts[i]);
                        }
                        return values;
                    }
                }
                catch
                {
                     // skip to make it invalid input
                }

                Print(context, Properties.Resources.RepeatInputMessage); 
                PrintLine(context);
                line = InputLine(context, printQuestion);
                
            }
            return null;
        }

        private static object ConvertTo(Type type, string s)
        {
            if (type == typeof(string))
                return s;
            else if (type == typeof(short))
            {
                return short.Parse(s);
            }
            else if (type == typeof(float))
            {
                return float.Parse(s);
            }
            else if (type == typeof(double))
            {
                return double.Parse(s);
            }

            throw new NotSupportedException();
        }

        public static string InputLine(CodeContext context, bool printQuestion)
        {
            if (printQuestion)
                Print(context, Properties.Resources.InputPrompt);
            string value = context.LanguageContext.DomainManager.SharedIO.InputReader.ReadLine();
            if (value == null) throw new DlrBasicException(Properties.Resources.InputIsExpected); 
            currentColumn = 0;
            return value;
        }

        public static void Print(CodeContext context, object o)
        {
            if(o == null) return;
            
            string s = (o is string) ? (string)o : (FormatNumber(o) + " ");

            context.LanguageContext.DomainManager.SharedIO.OutputWriter.Write(s);

            currentColumn += s.Length;
        }

        public static void PrintFormatted(CodeContext context, string format, object[] arguments)
        {
            StringBuilder sb = new StringBuilder();
            int i = 0;
            int j = 0;
            while (i < format.Length)
            {
                char ch = format[i++];
                if (ch == '!' || ch == '\\' || ch == '&')
                {
                    string s = (string)arguments[j++];
                    if (ch == '\\')
                    {
                        int i0 = i;
                        while (i < format.Length && format[i] == ' ') i++;
                        sb.Append(s.PadRight(i - i0 + 2));
                        if (i < format.Length && format[i] == '\\') i++;
                    }
                    else if (ch == '!')
                        sb.Append(String.IsNullOrEmpty(s) ? ' ' : s[0]);
                    else
                        sb.Append(s);
                }
                else if (ch == '#' || ch == '+' || ch == '%' ||
                    (i + 1 < format.Length && (format.Substring(i, 2) == "**" || format.Substring(i, 2) == "$$")))
                {
                    int i0 = i - 1;
                    while (i < format.Length && IsNumberFomattingSymbol(format[i]))
                    {
                        i++;
                    }
                    sb.Append(FormatNumber(format.Substring(i0, i - i0), arguments[j++]));
                }
                else if (ch == '_' && i < format.Length)
                    sb.Append(format[i++]);
                else
                    sb.Append(ch);
            }
            Print(context, sb.ToString());
        }

        public static string FormatNumber(object number)
        {
            double value;
            string s;
            if (number is short)
            {
                s = ((short)number).ToString();
                value = (short)number;
            }
            else if (number is float)
            {
                s = ((float)number).ToString();
                value = (float)number;
            }
            else
            {
                value = (double)number;
                s = value.ToString();
            }

            if (value >= 0) s = " " + s;
            return s;
        }

        public static string FormatNumber(string format, object number)
        {
            double value = (double)Convert.ChangeType(number, typeof(double));
            format = format.Replace("$$", "$").Replace("**", "*");
            int totalLength = format.Length;
            string sign = "";
            bool signAfter = false;
            if(format.IndexOf('+') >= 0)
            {
                signAfter = format.EndsWith("+");
                sign = value < 0 ? "-" : "+";
            }
            else if(format.EndsWith("-"))
            {
                signAfter = true;
                sign = value < 0 ? "-" : " ";               
            }
            else 
            {
                if(value < 0) sign = "-";
            }

            value = Math.Abs(value);
            format = format.Replace("+", "").Replace("-", "");

            int point = format.IndexOf('.');
            int precision = point < 0 ? 0 : format.Length - point - 1;

            bool isThouthSeparator = format.IndexOf(',') >= 0;
            string s = value.ToString( (isThouthSeparator ? "n" : "f") + precision.ToString());
            if (signAfter) s += sign; else s = sign + s;
            if (format.IndexOf('$') >= 0) s = "$" + s;
            if (format[0] == '*') s = s.PadLeft(totalLength, '*'); else s = s.PadLeft(totalLength);
            return s;
        }


        private static bool IsNumberFomattingSymbol(char ch)
        {
            return ch == '#' || ch == '+' || ch == '-' || ch == '*' || ch == '$'
                || ch == '%' || ch == '^' || ch == ',' || ch == '.';
        }

        const int TabWidth = 14;

        public static void PrintTab(CodeContext context)
        {
            int width = TabWidth - (CurrentColumn % TabWidth);
            Print(context, new String(' ', width));
        }

        public static void PrintLine(CodeContext context)
        {
            context.LanguageContext.DomainManager.SharedIO.OutputWriter.WriteLine();

            currentColumn = 0;
        }

        public static void Beep()
        {
            Console.Beep();
        }
    }
}
